package com.cloudfunx.common.util;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cloudfunx.common.constant.ConstConfig;
import com.cloudfunx.common.exception.BizException;
import com.cloudfunx.common.exception.BizRetStatus;
import com.cloudfunx.common.exception.TokenException;
import com.cloudfunx.common.vo.admin.AdminVo;
import com.cloudfunx.common.vo.user.UserVo;
import com.sun.media.sound.SoftTuning;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 对用户信息的存储进行封装，通过token存取用户信息
 * 对虚拟机内缓存和redis缓存进行封装，当没有配置redis时，启用虚拟机缓存
 * @author linjiayu
 */
@Slf4j
@Component
public class ServletRequestKit {

    /**
     * 小时
     */
    private static Integer HOURS;

    private static RedisTemplate<String, Object> REDIS_TEMPLATE;

    public ServletRequestKit(@Autowired(required = false) RedisTemplate<String, Object> redisTemplate,
                             @Value("${servletRequestKit.hours}") Integer hours) {
        HOURS = hours;
        if (redisTemplate == null) {
            log.info("redis 未初始化,采用LocalStoreCacheKit...");
            if (log.isDebugEnabled()) {
                // log.debug("采用万能token:KGyqNe15FTlPabvSOX9cbwumtUVsMFdnc7P8iM9sz7TVgUIL2KGf5A");
                // UserSessionVo userVo = new UserSessionVo();
                // userVo.setId(1390649571516727298L);
                // userVo.setPhone("13546881656");
                // userVo.setName("");
                // userVo.setImg("");
                // userVo.setRoleStatus(0);
                // userVo.setCode("");
                // userVo.setOpenFlag(1);
                // setUserVo("KGyqNe15FTlPabvSOX9cbwumtUVsMFdnc7P8iM9sz7TVgUIL2KGf5A", userVo);
                // AdminVo adminVo = new AdminVo();
                // BeanUtils.copyProperties(userVo, adminVo);
                // setAdminVo("KGyqNe15FTlPabvSOX9cbwumtUVsMFdnc7P8iM9sz7TVgUIL2KGf5A", adminVo);
            }
        } else {
            REDIS_TEMPLATE = redisTemplate;
        }
    }

    // public static void main(String[] args) {
    //     String s = ServletRequestKit.genToken(1390649571516727298L);
    //     System.out.println("token==" + s);
    // }

    public static HttpServletRequest getRequest() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Objects.requireNonNull(attrs, "request session not found");
        return attrs.getRequest();
    }

    public static HttpServletRequest getNotThrowRequest() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attrs == null ? null : attrs.getRequest();
    }

    public static AdminVo getAdminVo() {
        String format = String.format(ConstConfig.TOKEN_KEY, token());
        Object o = get(format);
        if (Objects.nonNull(o)) {
            return (AdminVo) o;
        }
        return null;
    }

    public static UserVo getUserVo() {
        String format = String.format(ConstConfig.APP_TOKEN_KEY, token());
        Object o = get(format);
        if (Objects.nonNull(o)) {
            return (UserVo) o;

        }
        return null;
    }

    public static UserVo getRequiredUserVo() {
        UserVo userVo = getUserVo();
        if (Objects.isNull(userVo)) {
            throw new TokenException(BizRetStatus.LOGIN_ERROR);
        }
        return userVo;
    }

    public static <T> T getUserVo(Class<T> clz) {
        UserVo userVo = getUserVo();
        if (Objects.nonNull(userVo)) {
            return (T) userVo;
        }
        return null;
    }

    public static <T> T getAdminVo(Class<T> clz) {
        AdminVo vo = getAdminVo();
        if (Objects.nonNull(vo)) {
            return (T) vo;
        }
        return null;
    }

    public static AdminVo getRequiredAdminVo() {
        String format = String.format(ConstConfig.TOKEN_KEY, token());
        Object o = get(format);
        if (Objects.isNull(o)) {
            throw new BizException("未找到管理员");
        }
        return (AdminVo) o;
    }

    public static String token() {
        return getRequest().getHeader("token");
    }

    /**
     * 通过拿到header里的token标志，来解析出用户id，see {@link #token()}
     * 推荐在controller层调用此方法，
     * token加密规则为e(id+random), see {{@link #genToken(Long)}}
     * @return id
     */
    public static Long tokenToId() {
        return Long.parseLong(tokenToIdString());
    }

    public static String tokenToIdString() {
        String token = token();
        if (StringUtils.isBlank(token)) {
            throw new TokenException(BizRetStatus.TOKEN_ERROR);
        }
        String s = EncryptionUtils.decryptString(token);
        return s.substring(0, s.length() - 13);
    }

    /**
     * 通过id生成token
     * @param id id
     * @return 返回token
     */
    public static String genToken(Long id) {
        return genToken(id.toString());
    }

    /**
     * 通过id生成token
     * @param id id
     * @return 返回token
     */
    public static String genToken(String id) {
        return IdkUtil.encrypt(id + "" + System.currentTimeMillis());
        // return IdkUtil.encrypt(id) + IdkUtil.encrypt(System.currentTimeMillis());
    }

    public static String genToken(String cacheKeyPrefix, Long id, Object vo) {
        String token = genToken(id);
        String format = String.format(cacheKeyPrefix, token);
        set(format, vo);
        return token;
    }

    /**
     * 设置普通用户信息
     * @param token 调用 {@link #genToken(Long)} 生成的token
     * @param vo    用户信息
     */
    public static void setUserVo(String token, Object vo) {
        setVo(ConstConfig.APP_TOKEN_KEY, token, vo);
    }

    /**
     * 设置管理员用户信息
     * @param token 调用 {@link #genToken(Long)} 生成的token
     * @param vo    用户信息
     */
    public static void setAdminVo(String token, Object vo) {
        setVo(ConstConfig.TOKEN_KEY, token, vo);
    }

    /**
     * 设置用户信息
     * @param cacheKeyPrefix 缓存标志，用来生成和token结合key
     * @param token          调用 {@link #genToken(Long)} 生成的token
     * @param vo             用户信息
     */
    public static void setVo(String cacheKeyPrefix, String token, Object vo) {
        String format = String.format(cacheKeyPrefix, token);
        set(format, vo);
    }

    public static Object getVo(String cacheKeyPrefix, String token) {
        String format = String.format(cacheKeyPrefix, token);
        return get(format);
    }

    public static <T> Object getRequiredVo(String cacheKeyPrefix, String token) {
        String format = String.format(cacheKeyPrefix, token);
        Object o = get(format);
        if (Objects.isNull(o)) {
            throw new TokenException(BizRetStatus.LOGIN_ERROR);
        }
        return (T) o;
    }

    /**
     * 移除vo
     * @param cacheKeyPrefix 缓存标志，用来生成和token结合key
     * @param token          调用 {@link #genToken(Long)} 生成的token
     */
    public static void removeVo(String cacheKeyPrefix, String token) {
        String format = String.format(cacheKeyPrefix, token);
        remove(format);
    }

    /**
     * 移除普通用户vo
     */
    public static void removeUserVo() {
        removeVo(ConstConfig.APP_TOKEN_KEY, token());
    }

    /**
     * 移除管理员用户vo
     */
    public static void removeAdminVo() {
        removeVo(ConstConfig.TOKEN_KEY, token());
    }

    private static Object get(String key) {
        if (Objects.nonNull(REDIS_TEMPLATE)) {

            Object o = REDIS_TEMPLATE.opsForValue().get(key);
            if (Objects.nonNull(o)) {
                return o;
            }
        } else {
            return LocalStoreCacheKit.get(key);
        }
        return null;
    }

    private static void set(String token, Object obj) {
        // hours
        if (Objects.nonNull(REDIS_TEMPLATE)) {
            REDIS_TEMPLATE.opsForValue().set(token, obj, HOURS, TimeUnit.HOURS);
        } else {
            LocalStoreCacheKit.set(token, obj, HOURS * 60 * 60);
        }
    }

    private static void remove(String key) {
        if (Objects.nonNull(REDIS_TEMPLATE)) {
            REDIS_TEMPLATE.delete(key);
        } else {
            LocalStoreCacheKit.remove(key);
        }
    }

}
